import React, { Component } from 'react'
import PropTypes from 'prop-types'
import throttle from 'utils/throttle'

const noop = () => {}

export class InfiniteScroll extends Component {
  static propTypes = {
    children: PropTypes.oneOfType([
      PropTypes.node,
      PropTypes.element,
    ]).isRequired,
    className: PropTypes.string,
    scrollDisabled: PropTypes.bool,
    immediateCheck: PropTypes.bool,
    onEndReached: PropTypes.func,
    distance: PropTypes.number,
  };

  static defaultProps = {
    className: '',
    scrollDisabled: false,
    immediateCheck: false,
    onEndReached: noop,
    distance: 50,
  };

  componentDidMount() {
    const element = this.el
    if (this.isAttached(element)) {
      this.doBind(element)
    }

    let bindTryCount = 0
    const tryBind = () => {
      if (bindTryCount > 10) return
      bindTryCount += 1
      if (this.isAttached(element)) {
        this.doBind(element)
      } else {
        setTimeout(tryBind, 50)
      }
    }

    tryBind()
  }

  componentWillUnmount() {
    if (this.scrollEventTarget) {
      this.scrollEventTarget.removeEventListener('scroll', this.scrollListener)
    }
  }

  getScrollEventTarget = (element) => {
    let currentNode = element
    const getComputedStyle = document.defaultView.getComputedStyle
    while (currentNode
      && currentNode.tagName !== 'HTML'
      && currentNode.tagName !== 'BODY'
      && currentNode.nodeType === 1
    ) {
      const overflowY = getComputedStyle(currentNode).overflowY
      if (overflowY === 'scroll' || overflowY === 'auto') {
        return currentNode
      }
      currentNode = currentNode.parentNode
    }
    return window
  };

  getScrollTop = (element) => {
    if (element === window) {
      return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop)
    }
    return element.scrollTop
  };

  getElementTop = (element) => {
    if (element === window) {
      return this.getScrollTop(window)
    }
    return element.getBoundingClientRect().top + this.getScrollTop(window)
  };

  getVisibleHeight = (element) => {
    if (element === window) {
      return document.documentElement.clientHeight
    }
    return element.clientHeight
  };

  doCheck = (force) => {
    const scrollEventTarget = this.scrollEventTarget
    const element = this.el
    const { distance, onEndReached, scrollDisabled } = this.props

    if (force !== true && scrollDisabled) return

    const viewportScrollTop = this.getScrollTop(scrollEventTarget)
    const viewportBottom = viewportScrollTop + this.getVisibleHeight(scrollEventTarget)
    let shouldTrigger = false

    if (scrollEventTarget === element) {
      shouldTrigger = scrollEventTarget.scrollHeight - viewportBottom <= distance
    } else {
      const elementBottom = (this.getElementTop(element) - this.getElementTop(scrollEventTarget)) + element.offsetHeight + viewportScrollTop
      shouldTrigger = viewportBottom + distance >= elementBottom
    }

    if (shouldTrigger && onEndReached) {
      onEndReached()
    }
  };

  doBind = (element) => {
    if (this.binded) return
    this.binded = true
    const { immediateCheck, scrollDisabled } = this.props

    this.scrollEventTarget = this.getScrollEventTarget(element)
    this.scrollListener = throttle(this.doCheck, 200)
    this.scrollEventTarget.addEventListener('scroll', this.scrollListener)

    if (!scrollDisabled && immediateCheck) {
      this.doCheck()
    }

    // if (!scrollDisabled) {
    //   this.doCheck()
    // }
  };

  isAttached = (element) => {
    let currentNode = element.parentNode
    while (currentNode) {
      if (currentNode.tagName === 'HTML') {
        return true
      }
      // DocumentFragment
      if (currentNode.nodeType === 11) {
        return false
      }
      currentNode = currentNode.parentNode
    }
    return false
  };

  render() {
    const { className, children } = this.props
    return (
      <div
        ref={(el) => { this.el = el }}
        className={className}
      >
        {children}
      </div>
    )
  }
}

export default InfiniteScroll
